#pragma once

#include <string>
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>

// Log Levels
enum class LogLevel { DEBUG, INFO, WARNING, ERROR, CRITICAL };

// A singleton logger class
class Logger {
 private:
    Logger();
    ~Logger();

    // Change Log Level to string.
    std::string levelToString(LogLevel level) const;

    // Get current time.
    std::string getCurrentTime() const;

    LogLevel m_level;              // Current log level.
    std::ofstream m_fileStream;    // File output stream.
    std::mutex m_mutex;            // Mutex for thread security.
    bool m_useConsole;             // Whether to output to the console.
    bool m_useFile;                // Whether to output to the file.

 public:
    // Delete copy constructor and assignment operator.
    Logger(const Logger&) = delete;
    Logger& operator= (const Logger&) = delete;

    // Get singleton instance.
    static Logger& getInstance();

    // Initialize logger system.
    void init(LogLevel level = LogLevel::INFO, const std::string& logFile = "");

    // Set log level.
    void setLevel(LogLevel level);

    // Log output.
    void log(LogLevel level, const std::string& message, const char* file, int line);

    // Check whether a log message of a certain level should be recorded.
    bool shouldLog(LogLevel level) const;
};

// Log macro definition.
#define LOG(level, message)                                                    \
  do {                                                                         \
    if (Logger::getInstance().shouldLog(level)) {                              \
      std::ostringstream oss;                                                  \
      oss << message;                                                          \
      Logger::getInstance().log(level, oss.str(), __FILE__, __LINE__);         \
    }                                                                          \
  } while (0)

#define LOG_DEBUG(message) LOG(LogLevel::DEBUG, message)
#define LOG_INFO(message) LOG(LogLevel::INFO, message)
#define LOG_WARNING(message) LOG(LogLevel::WARNING, message)
#define LOG_ERROR(message) LOG(LogLevel::ERROR, message)
#define LOG_CRITICAL(message) LOG(LogLevel::CRITICAL, message)